package oleg.lcdui;

/**
 * Copyright 2008 Oleg Frenkel
 *
 * This file is part of PerfectSearch.
 *
 * PerfectSearch is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * PerfectSearch is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with PerfectSearch.  If not, see <http://www.gnu.org/licenses/>.
 */

import javax.microedition.lcdui.*;
import java.util.*;

import oleg.C;
import oleg.cfg.Config;
import oleg.locale.LocaleManager;

public class TextListCanvas extends Canvas
{
  private final static int SPACE_INPUT_TEXT_AND_LIST = 0;
  private final static int SPACE_LIST_ITEMS = 0;

  private StringBuffer inputTextSB = new StringBuffer();
  private String displayText = "";
  private Command clearCommand = new Command("Clear", Command.EXIT, 1);
  private Command predOnCommand = new Command("Prediction on", Command.SCREEN, 3);
  private Command numCommand = new Command("Number mode", Command.SCREEN, 4);
  private Command predOffCommand = new Command("Prediction off", Command.SCREEN, 5);

  private Vector elems = new Vector();
  private int selItem = -1;
  private int indexOfTopItem = -1;

  private Font font;

  private InputTextProcessor inputTextProcessor = new InputTextProcessor(inputTextSB);

  private IKeyListener keyListener = null;

  // textModifiedListener is notified only when text is modified
  // as a result of user typing or user deleting the text
  private ITextModifiedListener textModifiedListener = null;

  // If true, then draw a vertical bar showing where next char is going to be entered
  // Right now is used to impliment a blinking cursor at the end of string.
  private boolean showTextCursor = true;

  // CommandListener
  private Vector commandListeners = new Vector();

  private Thread textBlinker = new TextBlinker();

  public TextListCanvas() {
    textBlinker.start();

    font = Font.getDefaultFont();

    addCommand(predOffCommand);
    addCommand(numCommand);
    addCommand(clearCommand);
    super.setCommandListener(new TLCommandListener());

    // Do some artificial initialization
    append("element1");
    append("element2");
    append("element3");
    append("element4");
    append("element5");
    append("element6");
    append("element7");
    append("element8");
    append("element9");
    append("element10");
    append("element11");
    append("element12");
    append("element13");
    append("element14");
    append("element15");
    append("element16");
    append("element17");
    append("element18");
    append("element19");
    append("element20");
    append("element21");
    append("element22");
    append("element23");
    append("element24");
    append("element25");
    append("element26");
  }

  public void updateWritingLang() {
    inputTextProcessor.updateWritingLang();
  }

  public void updateInterface() {
    removeCommand(predOffCommand);
    removeCommand(predOnCommand);
    removeCommand(numCommand);
    removeCommand(clearCommand);

    LocaleManager localeMgr = LocaleManager.instance();

    predOffCommand = new Command(localeMgr.getProperty(C.PREDICTION_OFF), Command.SCREEN, 5);
    numCommand = new Command(localeMgr.getProperty(C.NUMERIC), Command.SCREEN, 4);
    predOnCommand = new Command(localeMgr.getProperty(C.PREDICTION_ON), Command.SCREEN, 3);
    clearCommand = new Command(localeMgr.getProperty(C.CLEAR), Command.EXIT, 1);

    switch (Config.instance().getPadMode()) {
    case C.PAD_MODE_PRED_ON: 
      addCommand(numCommand);
      addCommand(predOffCommand);
      break;
    case C.PAD_MODE_PRED_OFF:
      addCommand(predOnCommand);
      addCommand(numCommand);
      break;
    case C.PAD_MODE_NUM:
      addCommand(predOnCommand);
      addCommand(predOffCommand);
      break;
    default:
      throw new RuntimeException("Unknown padMode: " + Config.instance().getPadMode());
    }
    

    addCommand(clearCommand);
  }

  ////////////////////////////////////////
  // METHODS REQUIRED BY APPLICATION
  ////////////////////////////////////////
  public void addCommandListener(CommandListener cl) {
    commandListeners.addElement(cl);
  }

  public void setCommandListener(CommandListener cl) {
    throw new RuntimeException("Cannot call TextListCanvas.setCommandListener()");
  }

  public void setKeyListener(IKeyListener keyListener) {
    this.keyListener = keyListener;
  }

  public void setTextModifiedListener(ITextModifiedListener textModifiedListener) {
    this.textModifiedListener = textModifiedListener;
  }

  public String getDisplayText() {
    return displayText;
  }

  public void setDisplayText(String newDisplayText) {
    if (!displayText.equals(newDisplayText)) {
      displayText = newDisplayText;
      repaint();
    }
  }

  public String getInputText() {
    return inputTextSB.toString();
  }

  public void deleteAll() {
    elems.removeAllElements();
    selItem = -1;
    indexOfTopItem = -1;

    repaint();
  }

  public void append(String item) {
    elems.addElement(item);
    if (selItem == -1) {
      selItem = 0;
    }

    if (indexOfTopItem == -1) {
      indexOfTopItem = 0;
    }

    repaint();
  }

  public int size() {
    return elems.size();
  }

  public int getSelectedIndex() {
    return selItem;
  }

  ////////////////////////////////////////
  // ABSTRACT METHODS INHERITED FROM Canvas
  ////////////////////////////////////////

  protected void paint(Graphics g) {
    int translatedY = 0;
    int fgColor = 0xFFFFFF;
    int bgColor = 0x1010FF;
    int invertRed = 100;
    int invertGreen = 100;
    int invertBlue = 250;

    int listScreenHeight = getHeight() - font.getHeight();
    int maxVisibleListItems = listScreenHeight/font.getHeight();

    // Paint background. It is necessary to paint clip area only.
    g.setColor(bgColor);
//     g.setColor(invertRed, invertGreen, invertBlue);
    g.fillRect(g.getClipX(), g.getClipY(), g.getClipWidth(), g.getClipHeight());

    // Paint input string. Use inverted colors
    g.setColor(fgColor);
    // The curve is 1/3th of the height
    int fontHeight = font.getHeight();
    g.fillRoundRect(10, 0, getWidth(), fontHeight, fontHeight/3, fontHeight/3);
    g.setColor(bgColor);
    int textIndent = 10 + fontHeight/3;
    g.drawString(displayText, textIndent, 0, Graphics.TOP|Graphics.LEFT);

    // Paint the bar in the end of string
    long lastTimeKeyEntered = inputTextProcessor.getLastTimeKeyEntered();
    if (showTextCursor && 
        System.currentTimeMillis() - lastTimeKeyEntered > 800) {
      g.fillRect(font.stringWidth(displayText) + textIndent, 0, 1, fontHeight);
    }

    g.translate(0, font.getHeight());
    translatedY += font.getHeight();

    // Draw a line between the text and list
    g.fillRect(0, 0, getWidth(), 2);
    g.translate(0, 2);
    translatedY += 2;

    g.setColor(fgColor);

    if (elems.size() > 0) {
      // Paint list items
 
      // We know the index of the top item that was last shown
      // We know that selected item should be visible
      // If top item is greater than selected item, make them
      // equal, exit.
      //
      // If both top item and selected item cannot be shown,
      // make the selected item the last item on the screen
      // and update top item.

      if (indexOfTopItem > selItem) {
        indexOfTopItem = selItem;
      } else if (selItem - indexOfTopItem + 1 > maxVisibleListItems) {
        indexOfTopItem = selItem - maxVisibleListItems + 1;
      }

      int firstItemToShow = indexOfTopItem;
//       int lastItemToShow = 
//         Math.min(elems.size() - 1, firstItemToShow + maxVisibleListItems - 1);

      for (int i = indexOfTopItem; i < elems.size(); i++) {
        String curElem = (String) elems.elementAt(i);

        if (i == selItem) {
          // Paint background for selected item
          g.fillRect(0, 0, getWidth(), font.getHeight());
          g.setColor(bgColor);
//           g.setColor(invertRed, invertGreen, invertBlue);
        }
        g.drawString(curElem, 0, 0, Graphics.TOP|Graphics.LEFT);
        g.setColor(fgColor);

        g.translate(0, font.getHeight());
        translatedY += font.getHeight();
      }
    }

    g.translate(0, -translatedY);
  }

  protected void keyPressed(int keyCode) {
//     inputTextSB.delete(0, inputTextSB.length());

    // if keyCode < 0 use getGameAction():
    //   FIRE: call number
    //   UP: move list up
    //   DOWN: move list down
    // if keyCode > 0 use keyCode to find out what KEY_... was pressed
    if (keyCode < 0) {
      // Process FIRE (make a call), UP and DOWN
      if (Canvas.UP == getGameAction(keyCode)) {
//         inputTextSB.append("UP");
        if (selItem > 0) {
          selItem--;
          repaint();
        }
      } else if (Canvas.DOWN == getGameAction(keyCode)) {
//         inputTextSB.append("Down");
        if (selItem < elems.size() - 1) {
          selItem++;
          repaint();
        }
      } else {
//         inputTextSB.append("p_" + keyCode + "_" + getGameAction(keyCode));
        repaint();
      }
    } else {
      // Process KEY_... event
      String previousText = inputTextSB.toString();
      inputTextProcessor.processKey(keyCode);
      // Notify listener that text was modified
      if (textModifiedListener != null && !previousText.equals(inputTextSB.toString())) {
        textModifiedListener.textModified(inputTextSB.toString());
      }
      repaint();
    }

    // call key listener
    if (keyListener != null) {
      keyListener.keyPressed(keyCode);
    }
  }

//   protected void keyReleased(int keyCode) {
//     inputTextSB.delete(0, inputTextSB.length());
//     inputTextSB.append("r_" + keyCode + "_" + getGameAction(keyCode));
//     repaint();
//   }

  private class TLCommandListener implements CommandListener
  {
    public void commandAction(Command c, Displayable s) {
      for (int i = 0; i < commandListeners.size(); i++) {
        CommandListener cl = (CommandListener)commandListeners.elementAt(i);
        cl.commandAction(c, s);
      }

      if (c == clearCommand) {
        if (inputTextSB.length() > 0) {
          inputTextSB.deleteCharAt(inputTextSB.length() - 1);
          if (textModifiedListener != null) {
            textModifiedListener.textModified(inputTextSB.toString());
          }
          repaint();
        }
        return;
      }
      
      if (c == predOnCommand) {
        Config.instance().setPadMode(C.PAD_MODE_PRED_ON);
        removeCommand(predOffCommand);
        removeCommand(numCommand);
        removeCommand(predOnCommand);

        addCommand(predOffCommand);
        addCommand(numCommand);
        return;
      }

      if (c == predOffCommand) {
        Config.instance().setPadMode(C.PAD_MODE_PRED_OFF);
        removeCommand(predOffCommand);
        removeCommand(numCommand);
        removeCommand(predOnCommand);

        addCommand(predOnCommand);
        addCommand(numCommand);
        return;
      }

      if (c == numCommand) {
        Config.instance().setPadMode(C.PAD_MODE_NUM);
        removeCommand(predOffCommand);
        removeCommand(numCommand);
        removeCommand(predOnCommand);

        addCommand(predOnCommand);
        addCommand(predOffCommand);
        return;
      }
    }
  }

  private class TextBlinker extends Thread
  {
    public void run() {
      while (true) {
        try {
          sleep(500);
          showTextCursor = !showTextCursor;
          repaint();
        } catch (InterruptedException ie) {
          // Do nothing.
        }
      }
    }
  }

}
